#pragma once
#include<iostream>
#include<string>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include"ThreadPool.hpp"
#include"Task.hpp"
#include"log.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        ACCEPT_ERR
    };
    const static int gbacklog = 5;
    const static uint16_t gport = 8080;
    class TcpServer
    {
        class ThreadData
        {
        public:
            ThreadData(TcpServer * const self, const int &sock)//TcpServer* &不行！
                : self_(self), accept_sockfd_(sock)
            {}
        public:
            TcpServer *self_;
            int accept_sockfd_;
        };
    public:
        TcpServer(const uint16_t &port = gport)
            : port_(port), listensock_(-1)
        {
        }

        void initServer()
        {
            //1.首先就是要创建套接字
            //1.1创建一个套接字！
            listensock_ = socket(AF_INET,SOCK_STREAM,0);//因为我们使用的是udp协议！所以是SOCK_STREAM——字节流！
            if(listensock_ == -1)
            {
                LogMessage(FATAL,"creat socket fail! ");
                exit(SOCKET_ERR);
            }

            LogMessage(NORMAL, "creat socket sucess! ");//这是一个正常的常规日志！
            //1.2进行绑定！
            struct sockaddr_in peer;
            bzero(&peer,sizeof(peer));
            peer.sin_family = AF_INET;//PF_INET也是可以的！两个是同一个的值不同的宏名字
            peer.sin_addr.s_addr = INADDR_ANY;//服务器ip不需要一个固定的地址！
            peer.sin_port = htons(port_);
            int n = bind(listensock_,(struct sockaddr*)&peer,sizeof(peer));

            //为什么我们再写UDP协议的时候，在recvfrom和sendto我们都没有讲过
            //主机转网络！网络转主机的概念呢？——我们只在创建套接字等会用到到struct sockaddr这个类型的时候会关注这一点！
            //首先我们要知道我们用接口的时候recvfrom和sendto是一个系统调用
            //系统调用在用的时候是不需要让用户知道主机是什么序列，网络是什么序列的！
            //简单的说——就是这些系统调用接口是在底层就已经给我们完成了转换！
            //那么为什么我们上面使用struct sockaddr相关类型的时候它不帮我们转换呢？
            //因为这个数据是一个结构体,所以对于系统来讲端口和ip是需要我们用户自己来进行转换的！
            //整个所有socket编程中！只有这个部分和接收client信息的时候从网络转主机，需要我们手动来进行转换
            if(n == -1)
            {
                LogMessage(FATAL, "bind socket fail! "); // 这是一个FATAL的日志
                exit(BIND_ERR);
            }

            LogMessage(NORMAL, "bind socket sucess! ");//这是一个正常的常规日志！

            //正常来说，如果是UDP那么我们到这一步其实已经结束了！但是这是TCP是面向链接的！
            //因为TCP是面向连接的！所以当别人是不能直接给我们发送数据的！
            //它要首先向我们发起连接！那么这意味着我们服务器首先要时时刻刻知道对方向我们发起了连接请求
            //那么如何知道对方向我们发起连接请求了呢？

            //3.设置socket 为监听状态！
           if (listen(listensock_, gbacklog) <  0)
           {
               LogMessage(FATAL, "listen socket fail! "); // 这是一个FATAL的日志
               exit(LISTEN_ERR);
           }
            //UDP是没有连接的，也不需要连接！发过来的数据就是数据！UDP直接就是recvfrom读取上来了！
            //而TCP是面向连接，面向连接的第一件事情就是建立连接！所以我们就必须保证tcp套接字处于监听状态！保证能够随时接收连接！
            LogMessage(NORMAL, "listen socket sucess! ");//这是一个正常的常规日志！
        }

        void start()
        {
            // version4——线程池版
            // 1.线程池初始化！
            ThreadPool<Task> *threadpool = ThreadPool<Task>::getInstance();
            threadpool->run(); // 直接让线程池启动起来！因为是线程池所以我们放在最前面！
            //刚开始没有任务的时候各个线程会现在条件变量下面进行等待！

            for(;;)
            {
                signal(SIGCHLD,SIG_IGN);//直接忽略子进程信号,那么操作系统就会自动回收
                //4.TCP服务器是不能那直接发送数据的！要首先获取连接！listen是为了得知谁向我们请求连接！
                //不能直接调用recvfrom
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(listensock_,(struct sockaddr*)&peer,&len);
                //这个套接字的作用是用于和client进行通信的
                //随着未来连接的增多，服务器上会有很多的套接字
                std::cout << sock << std::endl;
                if(sock == -1)
                {
                    LogMessage(ERROR, "accept fail! next"); //这是一个错误！但是不是一个很严重的错误！
                    //不影响进程的接下来的正常运行！
                    continue;//进行下一个循环继续进行连接！
                }
                LogMessage(NORMAL, "accept a link success!"); // 这是一个错误！但是不是一个很严重的错误！


                //version1
                // //这个sock是面向字节流的！——从这里开始未来都是文件操作！我们可以使用read，write这些文件描述的操作来进行！
                // ServerIO(sock);//对外提供服务
                // close(sock);//对于一个已经使用完毕的sock！我们必须要关闭！，不然就会导致
                //因为每次连接一个客户端，就要创建一个文件描述符！未来我们服务器上可能会挂着多个连接，占据多个描述符
                //文件描述符的本质就是数组下标！——下标是有上限的！
                //但是我们一般不用担心这个问题——一般来时我们可以打开6w多个文件可以使用ulimit -a指令来查看open file的多少
                //但是一般来说，连接个2.3千服务器本身就扛不住了！
                //但是我们还是要关闭！——因为不关闭会导致文件描述符泄漏！这样子下次获取链接就可以让其他的获取这个文件描述符了！


                //version2 ——多线程版
                //当我们进行fork的时候，子进程会继承父进程的文件描述符——而文件描述符本质是数组下标！所以执行的地址也是一样的！
                //即会指向同一份的文件！所以子进程是可以看到父进程的sockfd的！
                // pid_t id = fork();
                // if(id == 0)
                // {
                //     //这里是子进程！
                //     //因为父进程是打开了多个文件描述符！
                //     //每次accept后都会打开一个新的文件描述符！
                //     //而子进程也一并继承了这些文件描述符！
                //     //但是子进程是不需要那么多的文件描述符的！
                //     // 要将多余的文件描述符关闭！
                //     close(listensock_);//一定要关闭！一是会造成文件描述符泄漏！二是万一对文件进行误操作可能会出现未定义问题！
                //     //这个是关闭的是子进程自己的文件描述符表的listensock不会影响到父进程！
                //     // if(fork()>0) exit(0);//使用了信号我们也没有必要写这个代码了！
                //     //我们的在子进程里面再fork一次！
                //     //我们不要让子进程本身来提供这个服务！
                //     //而是让子进程的子进程来提供服务！——即孙子进程来提供这个服务！
                //     //因为子进程里面会返回——0，所以子进程会自动退出！让后面的主进程成功回收！
                //     //然后孙子进程执行完毕后会自动的退出！——但是因为此时子进程已经退出了！这就已经是一个孤儿进程！
                //     //这个进程会被操作系统给接管！和父进程也就没有关系了！资源会被操作系统给回收！

                //     ServerIO(sock);//对外提供服务
                //     close(sock); 
                //     exit(0);
                // }
                // close(sock);//这里是一定要加上的！关闭主进程的文件描述符！

                // //这里就是父进程——就要进行等待用来回收子进程的资源！
                // //否则越来越多的子进程会导致越来越多僵尸状态
                // // waitpid(id,nullptr,-1);//如果我们直接在这里等待！
                // //就会导致我们的代码依旧是串行的执行！——先让子进程执行！父进程等待，子进程执行完，父进程回收执行一下次循环！
                // //我们想要的是子进程执行的同时！父进程也一直执行！
                // //我们是可以使用轮询的方式进行非阻塞等待但是非阻塞等待有点不好！——如果一次性干100个连接，就会一次性创建100个子进程
                // //然后这样子就会让主进程一直卡在accept这个函数！因为没有连接的！那么我们创建的100个进程的进程状态就没有人回收了！
                // //所以单纯的非阻塞是不行的！
                // pid_t ret = waitpid(id,nullptr,0);//但是我们这里依旧选择单纯等待
                // if(ret > 0)
                // {
                //     LogMessage(NORMAL,"wait success!");
                // }
                //使用信号我们也美欧必要进行等待了！


                // //version 3——多线程版
                // //对于多线程，主线程和新线程的文件描述符都是共享的！因为用的都是同一个文件描述符表
                // //所以多线程这里是不用像多进程哪里一样关闭主线程的描述符！
                // pthread_t pid;
                // ThreadData *data = new ThreadData(this,sock);//要使用new！不能是一般的变量！会出现问题！

                // int n = pthread_create(&pid, nullptr, ThreadRoutine, data);
                // pthread_detach(pid);//进行线程分离！
                // // pthread_join(pid,nullptr);//如果我们在这里join就会出现阻塞和多线程一样的情况！
                // //这里我们该如何解决？只有线程执行完后才能进入下一个循环——所以我们不能阻塞等待！
                // //线程这里我们也没有非阻塞等待——我们可以直接进行线程分离！只要线程分离了就不用join它了！
                // //新线程跑完后会被自动回收！


                //version4 ——线程池
                threadpool->push(Task(sock,ServerIO));//将任务push进去！
                //使用sock和任务放进去！然后线程池就会自动的对任务进行处理！
            }
        }
        ~TcpServer()
        {
        }
    private:
        // static void *ThreadRoutine(void * args)//类内多线程要加上static
        // {
        //     ThreadData* data = static_cast<ThreadData*>(args);
        //     data->self_->ServerIO(data->accept_sockfd_);
        //     close(data->accept_sockfd_);//要在线程里面关闭掉用完的sockfd防止文件描述符泄漏！
        //     delete data;//释放掉该对象！
        //     return nullptr;
        // }

        // void ServerIO(int sockfd)
        // {
        //     char buffer[1024];
        //     while(true)
        //     {
        //         ssize_t n = read(sockfd, buffer, sizeof(buffer));
        //         //我们就跟读文件一样！就将数据读取出来了！——为什么udp不是？ 
        //         //因为udp不符合文件特性！——文件也是面向字节流的，和管道也是一样！udp是面向数据报的！要用特殊函数来读
        //         if(n > 0)
        //         {
        //             buffer[n] = 0;//我们目前把读取到的数据当做是字符串！
        //             std::cout << "recv message: " <<buffer <<std::endl;

        //             //服务端将数据发送回去！
        //             std::string outbuffer = buffer;
        //             //如何发送回去？——就是写入文件！write

        //             write(sockfd,outbuffer.c_str(),outbuffer.size());
        //         }
        //         else if(n ==0)
        //         {
        //             // read的返回值，在文件中返回值为0，意味着读取到文件尾
        //             //在网络中，返回值为0，意味着client端退出！
        //             LogMessage(NORMAL,"client quit ! me too!");
        //             break;
        //         }
        //     }
        // }
    private:
        int listensock_; // tcp服务端也是要有自己的socket的！这个套接字的作用不是用于通信的！而是用于监听连接的！
        uint16_t port_;//tcp服务器的端口

    };


}
